﻿namespace DataIntegration.Triggers.FileWatcher
{
    using System;
    using System.IO;
    using System.Reflection;
    using System.Threading;
    using Entities.Triggers;
    using log4net;
    using Messaging.Publisher;
    using Utils.Collections;

    public class FileWatcherTrigger : Trigger
    {
        private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().ReflectedType);

        private readonly FileSystemWatcher watcher;

        private readonly BlockingQueue<FileSystemEventArgs> queue;

        private readonly Thread queueReaderThread;

        private readonly object lastChangeLock;

        private DateTime lastChange;

        public FileWatcherTrigger(string name, IPublisherFactory publisherFactory, string path, string filter)
            : base(name, publisherFactory)
        {
            Logger.DebugFormat("Creating FileWatcherTrigger with path=[{0}] filter=[{1}]", path, filter);
            this.lastChange = DateTime.MinValue;
            this.lastChangeLock = new object();
            this.queue = new BlockingQueue<FileSystemEventArgs>();
            this.queueReaderThread = new Thread(this.ProcessQueue) { Name = string.Format("[{0}]-Worker", name) };
            this.watcher = new FileSystemWatcher(path, filter)
                               {
                                   NotifyFilter = NotifyFilters.LastWrite
                               };
            this.watcher.Changed += this.FileChanged;
        }

        public void ProcessQueue()
        {
            while (true)
            {
                try
                {
                    FileSystemEventArgs eventArgs = this.queue.Dequeue();
                    DateTime fileDate = new FileInfo(eventArgs.FullPath).LastWriteTimeUtc;

                    lock (this.lastChangeLock)
                    {
                        if (fileDate > this.lastChange)
                        {
                            Logger.InfoFormat("Firing trigger for [{0}]", eventArgs.Name);
                            this.InvokeFire(new TriggerInstruction());

                            this.lastChange = fileDate;
                        }
                        else
                        {
                            Logger.DebugFormat("Consuming trigger notification for [{0}] as has already been raised", eventArgs.Name);
                        }
                    }
                }
                catch (ThreadAbortException)
                {
                    Logger.Warn("Thread aborting");
                    break;
                }
            }
        }

        public void FileChanged(object sender, FileSystemEventArgs e)
        {
            this.queue.Enqueue(e);
        }

        protected override void StartTrigger()
        {
            this.queueReaderThread.Start();
            this.watcher.EnableRaisingEvents = true;
        }

        protected override void DisposeTrigger()
        {
            this.queueReaderThread.Abort();
            this.watcher.Dispose();
        }
    }
}